package com.helloniu.fl.upload {

import com.helloniu.fl.ByteWebHandler;
import com.helloniu.fl.util.Tool;
import flash.events.*;
import flash.net.*;
import flash.utils.ByteArray;

public class FileUploader {
    /**
     * Constructor.
     */
    public function FileUploader() {
    }

    /**
     * Set file reference.
     */
    public function setFileReference(afileReference:FileReference):void {
        this.fileReference = afileReference;
    }

    /**
     * Get file reference.
     */
    public function getFileReference():FileReference {
        return this.fileReference;
    }

    /**
     * Set upload url.
     */
    public function setMultThreads(amultThreads:int):void {
        this.multThreads = amultThreads;
    }

    /**
     * Get upload url.
     */
    public function getMultThreads():int {
        return this.multThreads;
    }

    /**
     * Set max file length(byte) able to be uploaded.
     * 1024 byte == 1KB, if 1024KB = 1M, 20M = 1024*1024*20(byte).
     */
    public function setMaxFileLength(amaxFileLength:Number):void {
        this.maxFileLength = amaxFileLength;
    }

    /**
     * Get max file length(byte) able to be uploaded.
     */
    public function getMaxFileLength():Number {
        return this.maxFileLength;
    }

    /**
     * Set upload url.
     */
    public function setUploadURL(auploadURL:String):void {
        this.uploadURL = auploadURL;
    }

    /**
     * Get upload url.
     */
    public function getUploadURL():String {
        return this.uploadURL;
    }

    /**
     * Set file upload state.
     */
    private function setState(astate:Number):void {
        state = astate;
    }

    /**
     * Get file upload state.
     */
    public function getState():Number {
        return state;
    }

    /**
     * Set file data length.
     */
    private function setFileLength(afileLength:uint):void {
        fileLength = afileLength;
    }

    /**
     * Get file data length.
     */
    public function getFileLength():uint {
        return fileLength;
    }

    /**
     * Set uploaded data length.
     */
    private function setUploadedTotal(auploadTotal:uint):void {
        uploadTotal = auploadTotal;
    }

    /**
     * Get file data length.
     */
    public function getUploadedTotal():uint {
        return uploadTotal;
    }

    /**
     * Get file data length.
     */
    public function cancel():void {
        pause();

        var barr:ByteArray = new ByteArray();
        var header:String = "CancelUploadFile";
        Tool.writeString(barr, header);
        Tool.writeString(barr, fileMD5ID);

        handler.setReqURL(uploadURL);
        handler.setReqData(barr);
        handler.executeCommunication();
    }

    public function startUpload(acallback:Function = null):void {
        this.callback = acallback;
        setState(ByteWebHandler.UNKNOWN);

        fileReference.addEventListener(Event.COMPLETE, loadOver, false,
                                       0, false);
        fileReference.addEventListener(IOErrorEvent.IO_ERROR, loadOver,
                                       false, 0, false);
        fileReference.addEventListener(SecurityErrorEvent.SECURITY_ERROR,
            loadOver, false, 0, false);

        // Load the file data to memory.
        fileReference.load();
    }

    private function loadOver(event:Event):void {
        fileReference.removeEventListener(Event.COMPLETE, loadOver);
        fileReference.removeEventListener(IOErrorEvent.IO_ERROR, loadOver);
        fileReference.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, loadOver);

        if(event is IOErrorEvent || event is SecurityErrorEvent) {
            setState(ByteWebHandler.TRANSACTION_FAILED);
            processOver();
        }
        else {
            fileData = fileReference.data;

            if(maxFileLength != -1) {
                if(maxFileLength < fileData.length) {
                    Tool.alert("the file is too large!");
                    return;
                }
            }

            setFileLength(fileData.length);
            getUploadInfo();
        }
    }

    /**
     * Get file upload info.
     */
    private function getUploadInfo():void {
        this.fileMD5ID = Tool.getFileMD5ID(fileReference);
        var barr:ByteArray = new ByteArray();
        var header:String = "getUploadInfo";
        Tool.writeString(barr, header);
        Tool.writeString(barr, fileMD5ID);

        handler.setReqURL(uploadURL);
        handler.setReqData(barr);
        handler.executeCommunication(getUploadInfoOver);
    }

    /**
     * Get file upload info handler.
     */
    private function getUploadInfoOver(ahandler:ByteWebHandler):void {
        if(ahandler.getState() != ByteWebHandler.COMMUNICATION_SUCCESSFUL) {
            setState(ByteWebHandler.COMMUNICATION_FAILED);
            processOver();
            return;
        }

        var barr:ByteArray = ahandler.getResData();
        barr.position = 0;
        var header:String = Tool.readString(barr);

        if(header != "UploadInfo") {
            setState(ByteWebHandler.TRANSACTION_FAILED);
            processOver();
            return;
        }

        var fileExisted:Boolean = barr.readInt() == 1;
        var uploadInfo:UploadInfo = new UploadInfo();

        if(fileExisted) {
            uploadInfo.readData(barr);
        }

        initUploadInfo(uploadInfo);
        fixUploadTotal(uploadInfo);

        if(uploadTotal == fileLength)  {
            setState(ByteWebHandler.COMMUNICATION_SUCCESSFUL);
            processOver();
            return;
        }

        // start uploading
        setState(ByteWebHandler.COMMUNICATION_SUCCESSFUL);
        processOver();

        uploadFile(uploadInfo);
    }

    /**
     * Fix file upload total.
     */
    private function fixUploadTotal(uploadInfo:UploadInfo):void {
        var blockInfos:Array = uploadInfo.blockInfos;

        if(blockInfos.length == 0) {
            uploadTotal = fileLength;
            return;
        }

        var noneUploadTotal:uint = 0;

        for(var i:int = 0; i < blockInfos.length; i++) {
            noneUploadTotal += (blockInfos[i] as BlockInfo).blockData.length;
        }

        uploadTotal = fileLength - noneUploadTotal;
    }

    /**
     * Init file upload info.
     */
    private function initUploadInfo(uploadInfo:UploadInfo):void {
        if(!uploadInfo.isEmpty()) {
            var arr:Array = uploadInfo.blockInfos;

            for(var i:int = 0; i < arr.length; i++) {
                var blockInfo:BlockInfo = arr[i] as BlockInfo;
                var dataLength:uint = blockInfo.endPos - blockInfo.startPos + 1;
                blockInfo.blockData = new ByteArray();

                fileData.readBytes(blockInfo.blockData, 0, dataLength);
            }

            return;
        }

        uploadInfo.fileMD5ID = fileMD5ID;
        uploadInfo.blockInfos = new Array();

        // @by tony, supports max size is 3G
        if(fileLength > 3000000000) {
            // show error
            return;
        }

        var blockSize:uint = parseInt(fileLength / multThreads + "");
        var startPos:uint = 0;
        var endPos:uint = 0;

        for(i = 0; i < multThreads; i++) {
            startPos = i* blockSize;
            endPos = startPos + blockSize - 1;

            if(i == (multThreads - 1)) {
                endPos = fileLength - 1;
            }

            blockInfo = new BlockInfo();
            blockInfo.startPos = startPos;
            blockInfo.endPos = endPos;
            blockInfo.blockData = new ByteArray();

            // load file data to block data
            fileData.readBytes(blockInfo.blockData, 0, endPos - startPos + 1);

            uploadInfo.blockInfos.push(blockInfo);
        }
    }

    /**
     * Upload the file.
     */
    private function uploadFile(uploadInfo:UploadInfo):void {
        var blockInfos:Array = uploadInfo.blockInfos;
        threads = new Array();

        for(var i:int = 0; i < blockInfos.length; i++) {
            var thread:FileBlockUploaderThread = new FileBlockUploaderThread(blockInfos[i], i);
            thread.setUploadURL(uploadURL);
            thread.setFileMD5ID(fileMD5ID);
            thread.setDataBlockSize(dataBlockSize);
            thread.startUpload(threadProgress);

            threads.push(thread);
        }
    }

    private function threadProgress(uploaderThread:FileBlockUploaderThread):void {
        setState(uploaderThread.getState());

        if(uploaderThread.getState() == ByteWebHandler.COMMUNICATION_SUCCESSFUL) {
            uploadTotal += uploaderThread.getUploadedTotal();
        }

        processOver();
    }

    /**
     * One process executing over.
     */
    private function processOver():void {
        if(callback != null) {
            (callback as Function).call(null, this);
        }
    }

    public function isPause():Boolean {
        return paused;
    }

    public function pause():void {
        paused = true;

        for(var i:int = 0; i < threads.length; i++) {
            (threads[i] as FileBlockUploaderThread).pause();
        }

    }

    public function resume():void {
        paused = false;

        for(var i:int = 0; i < threads.length; i++) {
            (threads[i] as FileBlockUploaderThread).resume();
        }
    }

    private var fileLength:uint = 0; // get 文件总字节数
    private var fileData:ByteArray;
    private var fileMD5ID:String
    private var fileReference:FileReference;
    private var uploadURL:String
    private var handler:ByteWebHandler = new ByteWebHandler();
    private var dataBlockSize:int = 1024;
    private var multThreads:int = 3;

    // net
    private var state:Number; // get， 上传状态
    private var callback:Function;
    private var uploadTotal:uint; // get, 已经上传字节数

    private var paused:Boolean = false; // controller
    private var threads:Array = new Array();

    private var maxFileLength:Number = -1;
}
}